package surface;

import primitives.AbstractShape;
import primitives.World3D;
import ray.Ray;
import reflection.ReflectionCalculation;
import vector.Vector;
import intersection.GroupIntersection;
import colour.Colour;

/**
 * The class integrates the function class of calculating the hitpoint' position
 * and normal vector,class of produce the pixel colour for object in each pixel
 * of final image, class of shadow and reflection to produce the array contain
 * all the final colour value for image
 * 
 * @author Yuting Wu
 * @author Kan Lin
 * @author Bing Dong
 * 
 */
public class ImageProducer {

	private World3D world;
	private Colour[][] pixels;
	private int height;
	private int width;

	private Ray[][] raylist;
	private Colour backGroundColour;
	private boolean ifShadowAndReflect;
	private int reflectionDepth;

	/**
	 * 
	 * @param world
	 *            contain inforamtion of the object and element need for
	 *            calculation
	 */
	public ImageProducer(World3D world) {
		this.world = world;
		this.raylist = world.getRaylist();
		this.height = raylist.length;
		this.width = raylist[0].length;
		this.backGroundColour = world.getBackground();
		this.ifShadowAndReflect = world.isIfShadowAndReflect();
		this.reflectionDepth = world.getReflectionDepth();
		pixels = new Colour[height][width];

	}

	/**
	 * return the result colour component
	 * 
	 * @param yPos
	 * @param xPos
	 * @return
	 */
	public Colour getPixelsIndex(int yPos, int xPos) {

		return pixels[yPos][xPos];
	}

	/**
	 * invoke the function for calculate the value for pixels in image
	 * 
	 * @param start
	 *            start part for calculation
	 * @param end
	 *            end part for calculation
	 */
	public void calPixels(int start, int end) {

		// calculate the nearest point
		GroupIntersection gHit = new GroupIntersection(world);

		System.out.println(Thread.currentThread().getName() + " is running...");

		// ColorProducer cp;
		for (int yPos = start; yPos < end; yPos++)
			for (int xPos = 0; xPos < width; xPos++) {
				boolean ifHit = gHit.calMutiIntersection(raylist[yPos][xPos]);
				// get the hit object
				AbstractShape obj = gHit.getObjHit();
				// hit point
				if (ifHit) {

					// get position and normal vector of the hitpoint
					Vector pHitPoint = gHit.getpHitpoint();
					Vector vHitPoint = gHit.getvHitpoint();

					// record the surface colour

					Colour col = obj.getColorAt(pHitPoint, vHitPoint);

					// check whether shade and reflect
					if (ifShadowAndReflect) {

						// calculate the shadow
						double tempPost = obj.multiLightIntensityCalculation(
								world, pHitPoint,vHitPoint,ifShadowAndReflect);

						// write the colour with shadow calcualtion
						writeOnPixel(col.multiply(tempPost), yPos, xPos);
						// non shadow
						if (tempPost - 0.0 > 0) {

							// check whehther the point is shaded. If so, there
							// is no need to calculate reflection

							double reflectVal = obj.getReflect();
							if (reflectVal - 0 >= 0.000001) {
								ReflectionCalculation rc = new ReflectionCalculation(
										pHitPoint, vHitPoint,
										raylist[yPos][xPos], world);
								Colour reflectionColor;

								reflectionColor = rc
										.calReflectValue(reflectionDepth);
								if (reflectionColor != null) {

									Colour tempPre = pixels[yPos][xPos];

									Colour temp = tempPre.add(reflectionColor
											.multiply(reflectVal));
									writeOnPixel(temp, yPos, xPos);
								}
							}
						}

					} else {
						// calculate without shadow
						double tempPost = obj.multiLightIntensityCalculation(
								world, pHitPoint,vHitPoint,ifShadowAndReflect);

						// write the colour with shadow calcualtion
						writeOnPixel(col.multiply(tempPost), yPos, xPos);
					}
				} else {
					writeOnPixel(backGroundColour, yPos, xPos);

				}
			}
	}

	/**
	 * change the value in pixels which should be protected when muti-thread
	 * 
	 * @param col
	 * @param yPos
	 * @param xPos
	 */
	private synchronized void writeOnPixel(Colour col, int yPos, int xPos) {

		pixels[yPos][xPos] = col;
	}

}
